/*
 * Copyright (C) 2012-2013 Michael L.R. Marques
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact: michaellrmarques@gmail.com
 */
package com.jm.jmsql.objects;

import com.jm.commons.cryptography.Crypto;
import com.jm.commons.logging.LogSystem;
import com.jm.commons.xpath.XPathNodeListIterator;
import com.jm.jmsql.objects.base.JMSqlObject;
import com.jm.jmsql.utils.Constants;
import com.jm.jmsql.utils.Settings;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 *
 * @created Nov 22, 2012
 * @author Michael
 */
public class Databases extends JMSqlObject {
    
    /**
     * 
     */
    public Databases() {
       this(false);
    }
    
    /**
     * 
     * @param loadDatabases 
     */
    public Databases(boolean loadDatabases) {
        this("Databases", loadDatabases);
    }
    
    /**
     * 
     * @param name
     * @param loadDatabases 
     */
    public Databases(String name, boolean loadDatabases) {
       super(name);
       if (loadDatabases) {
           load();
       }
    }
    
    /**
     * 
     */
    @Override
    public void load() {
        if (isLoaded()) {
            return;
        }
        // If the databases file does not exist, create a blank one
        if (!Settings.getDatabasesFile().exists()) {
            save();
        }
        // Start loading all databases from xml
        try {
            for(Node node : new XPathNodeListIterator(Settings.getDatabasesFile(), "/databases/database")) {
                if (node.hasAttributes()) {
                    NamedNodeMap map = node.getAttributes();
                    if (map.getNamedItem("library") == null) {
                        add(new Database(map.getNamedItem("name").getNodeValue(), map.getNamedItem("class").getNodeValue()));
                    } else {
                        add(new Database(map.getNamedItem("name").getNodeValue(), map.getNamedItem("class").getNodeValue(), map.getNamedItem("library").getNodeValue()));
                    }
                }
            }   
        } catch (XPathExpressionException | IOException e) {
            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Get all databases with XPath", e);
        }
        super.load();
    }

    /**
     * 
     * @return 
     */
    public boolean save() {
        // Create the directorys if they don't exist, delete the databases file just incase
        if (!Settings.getDatabasesFile().getParentFile().exists()) {
            Settings.getDatabasesFile().getParentFile().mkdirs();
        }
        // Check if the database file exists
        if (Settings.getDatabasesFile().exists()) {
            Settings.getDatabasesFile().delete();
        }
        // Re-create the database file 
        try {
            if (Settings.getDatabasesFile().createNewFile()) {
                try {
                    try (BufferedWriter writer = new BufferedWriter(new FileWriter(Settings.getDatabasesFile()))) {
                        writer.write(Constants.Xml.DEFAULT_DATABASES_XML);
                    }
                } catch (IOException ioe) {
                    LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Build the default databases xml document", ioe);
                    return false;
                }
            }
        } catch (IOException ioe) {
            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Create the new databases xml file", ioe);
            return false;
        }
        // Initialize the document build factory
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        // Create and initialize the document
        Document document = null;
        // Open the document builder
        try {
            document = factory.newDocumentBuilder().parse(Settings.getDatabasesFile());
        } catch (ParserConfigurationException | SAXException | IOException e) {
            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Build the databases xml file", e);
        }
        // Loop through databases
        for (JMSqlObject dbObject : this) {
            if (dbObject instanceof Database) {
                Database database = (Database) dbObject;
                if (database.isExternalDatabase()) {
                    try {
                        Node databaseNode = ((Node) XPathFactory.newInstance().newXPath().evaluate("/databases", document, XPathConstants.NODE)).appendChild(document.createElement("database"));
                        NamedNodeMap atts = databaseNode.getAttributes();
                        // Create the name attribute
                        Attr nameAttr = document.createAttribute("name");
                        nameAttr.setNodeValue(database.getName());
                        // Create the class attribute
                        Attr classAttr = document.createAttribute("class");
                        classAttr.setNodeValue(database.getClassName());
                        // Create the library attribute
                        Attr libraryAttr = document.createAttribute("library");
                        libraryAttr.setNodeValue(database.getLibrary().getPath());
                        // Add it to the named node map
                        atts.setNamedItem(nameAttr);
                        atts.setNamedItem(classAttr);
                        atts.setNamedItem(libraryAttr);
                    } catch (XPathExpressionException xpee) {
                        LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Create a database element", xpee);
                    }
                }
                for (JMSqlObject defObject : dbObject) {
                    if (defObject instanceof Definition) {
                        Definition definition = (Definition) defObject;
                        try {
                            Node definitionNode = ((Node) XPathFactory.newInstance().newXPath().evaluate("/databases/database[@name='" + database.getName() + "']", document, XPathConstants.NODE)).appendChild(document.createElement("definition"));
                            NamedNodeMap atts = definitionNode.getAttributes();
                            // Create the name attribute
                            Attr nameAttr = document.createAttribute("name");
                            nameAttr.setNodeValue(definition.getName());
                            // Create the datasource attribute
                            Attr datasourceAttr = document.createAttribute("datasource");
                            datasourceAttr.setNodeValue(definition.getDatasource());
                            // Create the username attribute
                            Attr usernameAttr = document.createAttribute("username");
                            usernameAttr.setNodeValue(definition.getUsername());
                            // Create the password attribute
                            Attr passwordAttr = document.createAttribute("password");
                            passwordAttr.setNodeValue(Crypto.encrypt(definition.getPassword()));
                            // Add it to the named node map
                            atts.setNamedItem(nameAttr);
                            atts.setNamedItem(datasourceAttr);
                            atts.setNamedItem(usernameAttr);
                            atts.setNamedItem(passwordAttr);
                        } catch (XPathExpressionException xpee) {
                            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Create a definition element", xpee);
                        }
                    }
                }
            }
        }
        // Write the document to the file
        try {
            TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(Settings.getDatabasesFile()));
            return true;
        } catch (TransformerException te) {
            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Save the databases xml document", te);
        }
        return false;
    }
    
    /**
     * 
     * @return 
     */
    @Override
    public Icon getIcon() {
        return new ImageIcon(getClass().getClassLoader().getResource("com/jm/jmsql/icons/dbxplora.png"));
    }
    
    /**
     * 
     * @return 
     */
    public List<Definition> getConnectedDefinitions() {
        List<Definition> definitions = new ArrayList();
        for (JMSqlObject database : this) {
            for (JMSqlObject definition : (Database) database) {
                if (((Definition) definition).isConnected()) {
                    definitions.add((Definition) definition);
                }
            }
        }
        return definitions;
    }
    
    /**
     * 
     * @return 
     */
    public List<Definition> getDefinitions() {
        List<Definition> definitions = new ArrayList();
        for (JMSqlObject database : this) {
            for (JMSqlObject definition : (Database) database) {
                definitions.add((Definition) definition);
            }
        }
        return definitions;
    }
    
    /**
     * 
     * @return 
     */
    @Override
    public JMSqlObject.Type getObjectType() {
        return JMSqlObject.Type.DATABASES;
    }
    
    /**
     * 
     * @return 
     */
    @Override
    public int getColumnCount() {
        return 2;
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override
    public String getColumnName(int index) {
        switch (index) {
            case 0: return "Name";
            case 1: return "Databases";
            default: return null;
        }
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override
    public Object getColumn(int index) {
        switch (index) {
            case 0: return getName();
            case 1: return size();
            default: return null;
        }
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override
    public boolean isColumnEditable(int index) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
    
    /**
     * 
     * @param index
     * @param object 
     */
    @Override
    public void setColumn(int index, Object object) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

}
